package jmu.tools.file.sync ;


import java.io.File ;
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
import java.io.FilenameFilter ;
import java.util.Scanner ;

import jmu.tools.file.sync.util.LogManager ;
import jmu.tools.file.sync.util.SyncResult ;
import jmu.tools.file.sync.util.ThreadManager ;
import jmu.tools.file.sync.util.LogManager.Type ;
import jmu.tools.file.sync.util.action.DownloadAction ;
import jmu.tools.file.sync.util.action.ScanningAction ;

import org.apache.tools.ant.BuildException ;
import org.apache.tools.ant.Task ;
import org.apache.tools.ant.types.EnumeratedAttribute ;


/**
 * @author vkushnir
 */
public class FileSyncTask extends Task
{
    public static class Force extends EnumeratedAttribute
    {
        @Override
        public String[] getValues ()
        {
            return new String[] { "true", "false" } ;
        }

        public boolean isEnabled ()
        {
            return Boolean.parseBoolean ( getValue () ) ;
        }
    }

    public static class LogFilter implements FilenameFilter
    {
        private String pattern = null ;

        public LogFilter ( final String pattern )
        {
            this.pattern = pattern ;
        }

        @Override
        public boolean accept ( final File paramFile, final String paramString )
        {
            if ( null == pattern )
            {
                return true ;
            }

            System.out.println ( paramFile.getAbsolutePath () ) ;
            System.out.println ( paramString ) ;

            return true ;
        }
    }

    private static final String DISABLED = "false" ;
    public static final int THREAD_COUNT_DEFAULT = 20 ;
    public static final int THREAD_COUNT_MIN = 1 ;
    public static final int THREAD_COUNT_MAX = 100 ;

    /**
     * Dummy runner
     * 
     * @param args
     */
    public static void main ( final String[] args )
    {
        final FileSyncTask t = new FileSyncTask () ;
        t.setThreads ( 15 ) ;
        t.setLog ( "log" ) ;
        t.setSrc ( "C:/Intel" ) ;
        //        t.setSrc ( "C:/_instructions" ) ;
        t.setDest ( "C:/_dest" ) ;

        t.execute () ;
    }

    // local fields
    private String src ;
    private String dest ;
    private Force force ;
    private int threads ;
    private String log ;

    /**
     * Create new task instance
     */
    public FileSyncTask ()
    {
        force = new Force () ;
        force.setValue ( DISABLED ) ;

        threads = THREAD_COUNT_DEFAULT ;
    }

    @Override
    public void execute () throws BuildException
    {
        // validate input
        validateNull ( "log", log ) ;
        validateNull ( "src", src ) ;
        validateNull ( "dest", src ) ;
        validateIsDirectory ( "log", log ) ;
        validateIsDirectory ( "src", src ) ;
        validateIsDirectory ( "dest", dest ) ;
        validateWrite ( "log", log ) ;
        validateRead ( "src", src ) ;
        validateRead ( "dest", dest ) ;
        validateWrite ( "dest", dest ) ;
        validateLimit ( "threads", threads, THREAD_COUNT_MIN, THREAD_COUNT_MAX ) ;

        // create result object
        final SyncResult result = new SyncResult () ;

        // create logger
        final LogManager logger = new LogManager ( log ) ;

        // create thread managers
        final ThreadManager sync = new ThreadManager ( threads,
                new DownloadAction ( logger, result, force.isEnabled () ) ) ;
        final ThreadManager scan = new ThreadManager ( 1, new ScanningAction ( logger, result, sync ) ) ;

        // before we proceed with scan we do need 
        // try to handle again fails from the previous run
        loadFile ( scan ) ;

        // do scan
        scan.add ( new File ( src ).getAbsoluteFile (), new File ( dest ).getAbsoluteFile () ) ;

        // perform actions 
        scan.process () ;
        sync.process () ;

        // print results
        result.print () ;

        // close loggers
        logger.close () ;
    }

    /**
     * Set destination directory
     * 
     * @param dest
     */
    public void setDest ( final String dest )
    {
        this.dest = dest ;
    }

    /**
     * Set force option
     * 
     * @param force
     */
    public void setForce ( final Force force )
    {
        this.force = force ;
    }

    public void setLog ( final String log )
    {
        this.log = log ;
    }

    /**
     * Set path to the source directory
     * 
     * @param src
     */
    public void setSrc ( final String src )
    {
        this.src = src ;
    }

    /**
     * Set number of synchronization threads
     * 
     * @param threads
     */
    public void setThreads ( final int threads )
    {
        this.threads = threads ;
    }

    private File findFile ( final String dir, final String pattern )
    {
        final File last = null ;
        final File[] logs = new File ( dir ).listFiles ( new LogFilter ( pattern ) ) ;
        for ( int i = 0 ; i < logs.length ; i++ )
        {
            System.out.println ( "item: " + logs[i].getAbsolutePath () ) ;
        }

        return last ;
    }

    private File findFile ( final Type type )
    {
        return findFile ( log, type.name ().toLowerCase ().replace ( '_', '-' ) ) ;
    }

    private void loadFile ( final ThreadManager scan )
    {
        // load failed files
        loadFile ( scan, findFile ( Type.FILE_FAIL ) ) ;

        // load failed directories
        loadFile ( scan, findFile ( Type.DIR_FAIL ) ) ;
    }

    private void loadFile ( final ThreadManager manager, final File file )
    {
        try
        {
            final Scanner s = new Scanner ( new FileInputStream ( file ) ) ;
            while ( s.hasNext () )
            {
                final String value = s.nextLine ().trim () ;
                if ( value.isEmpty () )
                {
                    continue ;
                }
                System.out.println ( " found pair: " + value ) ;
            }
        }
        catch ( final FileNotFoundException e )
        {
            System.out.println ( "can't read file: " + e.getLocalizedMessage () ) ;
        }
    }

    private void validateIsDirectory ( final String name, final String dir )
    {
        final File file = new File ( dir ) ;
        if ( !file.exists () || !file.isDirectory () )
        {
            throw new BuildException ( "Invalid value for '" + name + "'. '" + dir + "' is not a directory" ) ;
        }
    }

    private void validateLimit ( final String attribute, final int value, final int min, final int max )
    {
        if ( ! (min <= value && value <= max) )
        {
            throw new BuildException ( "Invalid value '" + value + "for '" + attribute + "'. Value must be in range {"
                    + min + ";}" + max + "" ) ;
        }
    }

    private void validateNull ( final String name, final String dir ) throws BuildException
    {
        if ( null == dir )
        {
            throw new BuildException ( "Attribute '" + name + "' is mandatory" ) ;
        }
    }

    private void validateRead ( final String name, final String dir )
    {
        final File file = new File ( dir ) ;
        if ( !file.exists () || !file.isDirectory () )
        {
            throw new BuildException ( "Invalid value for '" + name + "'. '" + dir + "' is not a directory" ) ;
        }
    }

    private void validateWrite ( final String name, final String dir )
    {
        final File file = new File ( dir ) ;
        if ( !file.exists () || !file.isDirectory () )
        {
            throw new BuildException ( "Invalid value for '" + name + "'. '" + dir + "' is not a directory" ) ;
        }
    }
}
